/******************************************************************************
 * Copyright 2020-2021 The Firmament Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#include "drv_i2c.h"
#include "hal/i2c/i2c.h"
#include "device.h"
#include "f28p65x_device.h"


// #define DRV_DBG(...) printf(__VA_ARGS__)
#define DRV_DBG(...)

/* We want to ensure the real-time performace, so the i2c timeout here is
 * relatively short */
#define MAX_TIMEOUT     10000

#define __I2C_CONFIG_INIT(i2c_base, speed, sda, scl)  \
    {                                                 \
        .parent.ops  = &i2c_bus_ops,                  \
        .i2c_periph  = i2c_base##_BASE,               \
        .sda_pin     = sda,                           \
        .sda_pin_mux = GPIO_##sda##_##i2c_base##_SDA, \
        .scl_pin     = scl,                           \
        .scl_pin_mux = GPIO_##scl##_##i2c_base##_SCL, \
        .i2c_clk     = SYSCTL_PERIPH_CLK_##i2c_base,  \
        .i2c_speed   = speed,                         \
    }

enum i2c_bus_id_e {
    I2C_BUS_0,
    I2C_BUS_1,
    I2C_BUS_2,
};

typedef struct __qx_i2c_bus {
    struct rt_i2c_bus parent;
    uint32_t i2c_periph;
    uint32_t sda_pin;
    uint32_t sda_pin_mux;
    uint32_t scl_pin;
    uint32_t scl_pin_mux;
    uint32_t i2c_clk;
    uint32_t i2c_speed;
} qx_i2c_bus_t;

static rt_size_t i2c_master_transfer(struct rt_i2c_bus* bus, rt_uint16_t slave_addr, struct rt_i2c_msg msgs[], rt_uint32_t num)
{
    struct rt_i2c_msg* msg;
    uint32_t msg_idx = 0;
    qx_i2c_bus_t* i2c_bus = (qx_i2c_bus_t*)bus;
    uint64_t start_us;
    uint32_t base = i2c_bus->i2c_periph;
    uint32_t i = 0, timeout = MAX_TIMEOUT;

    slave_addr = (slave_addr >> 1); // convert 8bit address to 7bit address;

    //
    // Wait until the STP bit is cleared from any previous controller
    // communication. Clearing of this bit by the module is delayed until after
    // the SCD bit is set. If this bit is not checked prior to initiating a new
    // message, the I2C could get confused.
    //
    if (I2C_getStopConditionStatus(base))
    {
        goto _stop;
    }

    // Check bus free idle
    if (I2C_isBusBusy(base))
    {
        goto _stop;
    }

    /*------------------------------------------------------------------------*/
    /* start/restart read operation */
    I2C_setConfig(base, I2C_CONTROLLER_SEND_MODE | I2C_REPEAT_MODE);

    /* start signal */
    I2C_sendStartCondition(base);

    if (msg->flags & RT_I2C_ADDR_10BIT) {
        /* send 10bit slave address to I2C bus */
        I2C_setAddressMode(base, I2C_ADDR_MODE_10BITS);
        I2C_setTargetAddress(base, slave_addr);
    } else {
        /* send 7bit slave address to I2C bus */
        I2C_setTargetAddress(base, slave_addr);
        // printf("I2C send 7bit addr:0x%02x\n", slave_addr);
    }

    /*------------------------------------------------------------------------*/
    for (msg_idx = 0; msg_idx < num; msg_idx++) {
        msg = &msgs[msg_idx];
        uint16_t nbytes = msg->len;

        if (msg->flags & RT_I2C_RD) { // read
            /* restart signal */
            I2C_sendStartCondition(base);

            /* set receive */
            I2C_setConfig(base, I2C_CONTROLLER_RECEIVE_MODE);
            I2C_setDataCount(base, nbytes);

            /* restart signal */
            I2C_sendStartCondition(base);

            for (i = 0; i < nbytes; i++) {
                timeout = MAX_TIMEOUT;
                while ((!(I2C_getStatus(base) & I2C_STS_RX_DATA_RDY)) && timeout--);
                if (0 == timeout) {
                    DRV_DBG("I2C wait TX_DATA_RDY timeout\n");
                    goto _stop;
                }

                msg->buf[i] = I2C_getData(base);
                I2C_clearStatus(base, I2C_STS_RX_DATA_RDY);
            }

        } else { // write
            /* write n Bytes data */
            for (i = 0; i < nbytes; i++) {
                I2C_putData(base, msg->buf[i]);
                timeout = MAX_TIMEOUT;
                while (((I2C_getStatus(base) & I2C_STS_TX_DATA_RDY)) && timeout--);

                // printf("I2C write[%d] = %02x\n", i, msg->buf[i]);

                if (0 == timeout) {
                    DRV_DBG("I2C wait TX_DATA_RDY timeout\n");
                    goto _stop;
                }
            }
        }
    }

_stop:
    /* send a stop condition to I2C bus */
    I2C_sendStopCondition(base);

    /* wait until stop flag is set */
    start_us = systime_now_us();
    while (!I2C_getStopConditionStatus(base)) {
        if ((systime_now_us() - start_us) >= MAX_TIMEOUT) {
            DRV_DBG("I2C wait stop timeout\n");
            break;
        }
    }

    return msg_idx;
}

/*------------------------------------------------------------------*/
static const struct rt_i2c_bus_device_ops i2c_bus_ops = {
    i2c_master_transfer,
    RT_NULL,
    RT_NULL
};

/* i2c bus instances */
static qx_i2c_bus_t i2c_bus[] = {
    // ----------------------------- base speed     sda   scl
    [I2C_BUS_0] = __I2C_CONFIG_INIT(I2CA, 100000,   42,   43),
};

/* i2c device instances */
static struct rt_i2c_device i2c0_dev0 = {
    .slave_addr = 0x45, /* AW2023 7 bit address */
    .flags = 0
};

static struct rt_i2c_device i2c0_dev1 = {
    .slave_addr = 0x28, /* MS4525 7 bit address */
    .flags = 0
};

static void i2c_init(uint8_t bud_id)
{
    qx_i2c_bus_t *pi2c = &i2c_bus[bud_id];
    uint32_t base = pi2c->i2c_periph;

    /* i2c gpio */
    // printf("I2C[%d]: sda=%d, scl=%d, speed=%d \r\n", bud_id, pi2c->sda_pin, pi2c->scl_pin, pi2c->i2c_speed);

    GPIO_setPadConfig(pi2c->sda_pin, GPIO_PIN_TYPE_OD);
    GPIO_setQualificationMode(pi2c->sda_pin, GPIO_QUAL_ASYNC);
    GPIO_setPinConfig(pi2c->sda_pin_mux);

    GPIO_setPadConfig(pi2c->scl_pin, GPIO_PIN_TYPE_OD);
    GPIO_setQualificationMode(pi2c->scl_pin, GPIO_QUAL_ASYNC);
    GPIO_setPinConfig(pi2c->scl_pin_mux);

    /* i2c config */
    SysCtl_enablePeripheral(pi2c->i2c_clk);

    I2C_disableModule(base);
    I2C_disableLoopback(base);
    I2C_initController(base, DEVICE_SYSCLK_FREQ, pi2c->i2c_speed, I2C_DUTYCYCLE_50);
    I2C_setBitCount(base, I2C_BITCOUNT_8);
    I2C_setAddressMode(base, I2C_ADDR_MODE_7BITS);
    I2C_disableFIFO(base);
    I2C_enableModule(base);
}

static void i2c_hw_init(void)
{
    i2c_init(I2C_BUS_0);
}

rt_err_t drv_i2c_init(void)
{
    /* i2c low-level initialization */
    i2c_hw_init();

    /* register i2c bus */
    RT_TRY(rt_i2c_bus_device_register(&i2c_bus[I2C_BUS_0].parent, "i2c0"));

    /* attach i2c devices */
    RT_TRY(rt_i2c_bus_attach_device(&i2c0_dev0, "i2c0_dev0", "i2c0", RT_NULL));

    /* attach i2c devices */
    RT_TRY(rt_i2c_bus_attach_device(&i2c0_dev1, "i2c0_dev1", "i2c0", RT_NULL));

    return RT_EOK;
}
